package com.jsyso.jsyso.util;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.beans.BeanMap;

/**
 * JSyso Map
 * @author janjan, xujian_jason@163.com
 *
 */
public class JsMap implements Map<String, Object>, Cloneable, Serializable, InvocationHandler {
	private static final long serialVersionUID = 1L;
	private Logger logger = LoggerFactory.getLogger(JsMap.class);
	private static final int DEFAULT_INITIAL_CAPACITY = 16;
	
	private final Map<String, Object> map;
	
	public JsMap() {
		this(false, DEFAULT_INITIAL_CAPACITY);
	}
	
	public JsMap(boolean ordered, int initialCapacity) {
		if (ordered) {
            this.map = new LinkedHashMap<String, Object>(initialCapacity);
        } else {
        	this.map = new HashMap<String, Object>(initialCapacity);
        }
	}
	
	public JsMap(int initialCapacity) {
		this(false, initialCapacity);
	}
	
	public JsMap(Map<String, Object> map) {
		this.map = map;
	}
	
	public static JsMap create() {
		return new JsMap();
	}
	
	public static JsMap create(int initialCapacity) {
		return new JsMap(initialCapacity);
	}
	
	public static JsMap create(String key, Object value) {
		return new JsMap().set(key, value);
	}
	
	public static JsMap create(Map<String, Object> map) {
		JsMap jsMap = new JsMap();
		if(map != null)
			jsMap.putAll(map);
		return jsMap;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Class<?>[] parameterTypes = method.getParameterTypes();
		// set
		if (parameterTypes.length == 1) {
            if (method.getName().equals("equals")) {
                return this.equals(args[0]);
            }
            
            Class<?> returnType = method.getReturnType();
            if (returnType != void.class) {
            	logger.warn("[JsMap illegal setter] returnType != void.class");
            	return null;
            }

            String name = method.getName();
            if (!name.startsWith("set")) {
            	logger.warn("[JsMap illegal setter] !name.startsWith(set)");
            	return null;
            }
            
            name = name.substring(3);
            if (name.length() == 0) {
            	logger.warn("[JsMap illegal setter] No executable method");
            	return null;
            }
            
            name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
            map.put(name, args[0]);
            return null;
        }
		// get
		if (parameterTypes.length == 0) {
            Class<?> returnType = method.getReturnType();
            if (returnType == void.class) {
            	logger.warn("[JsMap illegal getter] returnType == void.class");
            	return null;
            }

            String name = method.getName();
            if (name.startsWith("get")) {
                name = name.substring(3);
                if (name.length() == 0) {
                	logger.warn("[JsMap illegal setter] No executable method");
                	return null;
                }
                name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
            } else if (name.startsWith("is")) {
                name = name.substring(2);
                if (name.length() == 0) {
                	logger.warn("[JsMap illegal setter] No executable method");
                	return null;
                }
                name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
            } else if (name.startsWith("hashCode")) {
                return this.hashCode();
            } else if (name.startsWith("toString")) {
                return this.toString();
            } else {
            	logger.warn("[JsMap illegal setter] No executable method");
            	return null;
            }
            Object value = map.get(name);
            return CastUtils.cast(value, method.getGenericReturnType());
        }
		return null;
	}

	@Override
	public int size() {
		return map.size();
	}

	@Override
	public boolean isEmpty() {
		return map.isEmpty();
	}

	@Override
	public boolean containsKey(Object key) {
		return map.containsKey(key);
	}
	
	public void containsKey(Object key, Callback<Object, Object> callback) {
		if(this.containsKey(key)) {
			callback.handle(this.get(key));
		}
	}

	@Override
	public boolean containsValue(Object value) {
		return map.containsValue(value);
	}

	@Override
	public Object get(Object key) {
		return map.get(key);
	}

	public <T> T get(Object key, Class<T> clazz) {
		Object value = map.get(key);
		return CastUtils.cast(value, clazz);
	}
	
	public <T> T get(Object key, T defaultValue, Class<T> clazz) {
		Object value = map.get(key);
		T newValue = CastUtils.cast(value, clazz);
		return newValue == null ? defaultValue : newValue;
	}
	
	@Override
	public Object put(String key, Object value) {
		return map.put(key, value);
	}
	
	public JsMap set(String key, Object value) {
		map.put(key, value);
		return this;
	}

	@Override
	public Object remove(Object key) {
		return map.remove(key);
	}
	
	public void removes(Object... keys) {
		if(keys == null || keys.length == 0) {
			return ;
		}
		for(Object key : keys) {
			this.remove(key);
		}
	}
	
	public JsMap delete(Object key) {
		map.remove(key);
		return this;
	}

	@Override
	public void putAll(Map<? extends String, ? extends Object> m) {
		map.putAll(m);
	}

	@Override
	public void clear() {
		map.clear();
	}

	@Override
	public Set<String> keySet() {
		return map.keySet();
	}

	@Override
	public Collection<Object> values() {
		return map.values();
	}

	@Override
	public Set<java.util.Map.Entry<String, Object>> entrySet() {
		return map.entrySet();
	}

	@Override
	public Object clone() {
		return new JsMap(map instanceof LinkedHashMap 
				? new LinkedHashMap<String, Object>(map) 
						: new HashMap<String, Object>(map));
	}
	
	@Override
	public boolean equals(Object obj) {
		return map.equals(obj);
	}
	
	@Override
	public int hashCode() {
		return map.hashCode();
	}
	
	@Override
	public String toString() {
		return this.map.toString();
	}

	public Object getBean() {
		BeanGenerator beanGenerator = new BeanGenerator();
		for(String key : this.map.keySet()) {
			Object value = this.map.get(key);
			if(value == null) 
				value = "";
			beanGenerator.addProperty(key, value.getClass());
		}
		Object bean = beanGenerator.create();
		BeanMap beanMap = BeanMap.create(bean);
		beanMap.putAll(this.map);
		return bean;
	}
	
}
